package com.monkey.web.context;

import com.google.common.collect.Lists;
import com.monkey.core.exception.MonkeyException;
import com.monkey.core.plugin.dto.MonkeyDto;
import com.monkey.core.plugin.web.validator.ConstraintType;
import com.monkey.core.plugin.web.validator.ParameterConstraint;
import com.monkey.core.plugin.web.validator.ParameterConstraints;
import com.monkey.core.web.ServletRequestContextHolder;
import com.monkey.core.web.response.MonkeyResponseDto;
import com.monkey.utils.MonkeyUtils;
import com.monkey.utils.WebUtils;
import com.monkey.web.context.strategy.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.json.MonkeyFastJsonView;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * 1. 处理自定义异常MonkeyException
 * 2. 处理请求传过来的fields参数，由前端指定返回哪些数据，多个field用逗号间隔
 * eg: http://xxx.com/user/info?fields=id,name,age,teacher[id:name:age]
 * 返回的数据格式：
 * ｛
 * "id":1,
 * "name":"Wu Tian Qiang",
 * "age:24,
 * "teacher":{
 * "id":2,
 * "name":"SilentWu",
 * "age":35
 * }
 * ｝
 * 3. 完成monkeyDto的数据校验工作
 * <p/>
 * Project  : monkey
 * Author   : Wu Tian Qiang
 * Date     : 2016/8/31
 */
@Component
@Aspect
public class ControllerAspect {

    private static final String FIELDS_PARAM = "fields";
    private CtrlReturnValueResolve ctrlReturnValueResolve;

    public ControllerAspect() {
        ctrlReturnValueResolve = new CtrlReturnValueResolve();
        ctrlReturnValueResolve.addHandler(new BaseEntityCtrlReturnValueHandler())
                .addHandler(new MonkeyResponseDtoCtrlReturnValueHandler())
                .addHandler(new ListCtrlReturnValueHandler())
                .addHandler(new PageCtrlReturnValueHandler())
                .addHandler(new MessageCtrlReturnValueHandler());

    }

    @Pointcut("execution(@org.springframework.web.bind.annotation.RequestMapping * com.monkey.web..*.*(..))")
    private void controllerPoint() {
    }

    /**
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "controllerPoint()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequest request = ServletRequestContextHolder.getRequest();
        if (!WebUtils.isAjaxRequest((HttpServletRequest) request)) {
            return joinPoint.proceed();
        }
        MonkeyResponseDto jsonData;
        try {
            validateParameters(joinPoint);


            String fieldStr = request.getParameter(FIELDS_PARAM);
            Object result = joinPoint.proceed();
            if (result == null) {
                return null;
            }
            //1. 返回MonkeyResponseDto
            //2. 返回BaseEntity  Treeable
            //2. 返回List<BaseEntity>   List<Treeable>
            //3. 返回Page<BaseEntity>   Page<Treeable>
            jsonData = ctrlReturnValueResolve.resolve(result, fieldStr);
            if (MonkeyUtils.isEmpty(jsonData)) {
                return result;
            }
        } catch (MonkeyException e) {
            jsonData = new MonkeyResponseDto().fail(e.getMessage());
        }
        request.setAttribute(MonkeyFastJsonView.DATA_KEY, jsonData);
        return null;
    }

    private void validateParameters(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        Method method = BeanUtils.findMethodWithMinimalParameters(signature.getDeclaringType(), signature.getName());

        List<ParameterConstraint> constraints = new ArrayList<>();
        constraints.addAll(resolveParameterConstraint(method));
        constraints.addAll(resolveParameterConstraints(method));
        if (constraints.isEmpty()) {
            return;
        }

        MonkeyDto monkeyDto = resolveArgMonkeyDto(joinPoint);
        if (monkeyDto == null) {
            return;
        }

        validate(monkeyDto, constraints);
    }

    private void validate(MonkeyDto monkeyDto, List<ParameterConstraint> constraints) {
        for (ParameterConstraint constraint : constraints) {
            for (ConstraintType constraintType : constraint.constraintType()) {
                constraintType.validate(monkeyDto, constraint);
            }
        }
    }

    private MonkeyDto resolveArgMonkeyDto(ProceedingJoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof MonkeyDto) {
                return (MonkeyDto) arg;
            }
        }
        return null;
    }

    private Collection<? extends ParameterConstraint> resolveParameterConstraints(Method method) {
        ParameterConstraints parameterConstraints = method.getAnnotation(ParameterConstraints.class);
        if (parameterConstraints == null) {
            return Lists.newArrayList();
        }
        return Arrays.asList(parameterConstraints.value());
    }

    private Collection<? extends ParameterConstraint> resolveParameterConstraint(Method method) {
        ParameterConstraint parameterConstraint = method.getAnnotation(ParameterConstraint.class);
        if (parameterConstraint == null) {
            return Lists.newArrayList();
        }
        return Lists.newArrayList(parameterConstraint);
    }

}
